#include <stdio.h>
#include <stdint.h>
#include <ctype.h>
#include <string.h>
#include "hexdump.h"

int is_power_of_2(unsigned long n)
{
    return (n != 0 && ((n & (n-1)) == 0));
}

char hex_asc[] = "0123456789abcdef";
#define hex_asc_hi(x) hex_asc[((x) & 0x0f)]
#define hex_asc_lo(x) hex_asc[((x) & 0xf0) >> 4]

int hex_dump_to_buffer(const void *buf, int len, int rowsize, int groupsize, 
                       char *linebuf, int linebuflen, int ascii)
{
    const char *ptr = (char *)buf;
    int ngroups;
    char ch;
    int j, lx = 0;
    int ascii_column;
    int ret;

    if (rowsize != 16 && rowsize != 16) {
        rowsize = 16;
    }

    if (len > rowsize) {
        len = rowsize;
    }

    if (!is_power_of_2(groupsize) || groupsize > 8) 
    {
        groupsize = 1;
    }

    if ((len % groupsize) != 0) {
        groupsize = 1;
    }

    ngroups = len / groupsize;
    ascii_column = rowsize * 2 + rowsize / groupsize + 1;

    if (!linebuflen) {
        goto overflow1;
    }

    if (!len) {
        goto nil;
    }

    if (groupsize == 8) {
        uint64_t *ptr8 = (uint64_t *)buf;

        for (j = 0; j < ngroups; j++) {
            ret = snprintf(linebuf + lx, linebuflen - lx,
                           "%s%16.16llx", j ? " " : "",
                           ptr8 + j);
            if (ret >= linebuflen - lx) {
                goto overflow1;
            }
            lx += ret;
        }
    } else if (groupsize == 4) {
        uint32_t *ptr4 = (uint32_t *)buf;

        for(j = 0; j < ngroups; j++) {
            ret = snprintf(linebuf + lx, linebuflen - lx,
                           "%s%8.8x", j ? " " : "",
                           ptr4 + j);
            if (ret >= linebuflen - lx) {
                goto overflow1;
            }
            lx += ret;
        }
    } else if (groupsize == 2) {
        uint16_t *ptr2 = (uint16_t *)buf;

        for (j = 0; j < ngroups; j++) {
            ret = snprintf(linebuf + lx, linebuflen - lx,
                           "%s%4.4x", j ? " ": "",
                           ptr2 + j);
            if (ret >= linebuflen - lx) {
                goto overflow1;
            }
            lx += ret;
        }
    } else {
        for (j = 0; j < len; j++) {
            if (linebuflen < lx + 2) {
                goto overflow2;
            }
            ch = ptr[j];
            linebuf[lx++] = hex_asc_hi(ch);
            if (linebuflen < lx + 2) {
                goto overflow2;
            }
            linebuf[lx++] = hex_asc_lo(ch);
            if (linebuflen < lx + 2) {
                goto overflow2;
            }
            linebuf[lx++] = ' ';
        }

        if (j) {
            lx --;
        }
    }

    if (!ascii) {
        goto nil;
    }

    while (lx < ascii_column) {
        if (linebuflen < lx + 2) {
            goto overflow2;
        }
        linebuf[lx++] = ' ';
    }

    for (j = 0; j < len; j++) {
        if (linebuflen < lx + 2) {
            goto overflow2;
        }
        ch = ptr[j];
        linebuf[lx++] = (isprint(ch) ? ch : '.');
    }

nil:
    linebuf[lx] = '\0';
    return lx;
overflow2:
    linebuf[lx++] = '\0';
overflow1:
    return ascii ? ascii_column + len : (groupsize * 2 + 1) * ngroups - 1;
}

void print_hex_dump(const char *level, const char *prefix_str, int prefix_type,
                    int rowsize, int groupsize,
                    const void * buf, int len, int ascii)
{
    const char *ptr = (char *)buf;
    int i, linelen, remaining = len;
    unsigned char linebuf[32 * 3 + 2 + 32 + 1];

    if (rowsize != 16 && rowsize != 32) {
        rowsize = 16;
    }

    for (i = 0; i < len; i += rowsize) {
        linelen = remaining > rowsize ? rowsize : remaining;
        remaining -= rowsize;

        (void)hex_dump_to_buffer(ptr + i, linelen, rowsize, groupsize,
                           linebuf, sizeof(linebuf), ascii);

        switch (prefix_type) {
        case DUMP_PREFIX_ADDRESS:
            printf("%s %s %p: %s\n", level, prefix_str, ptr + i, linebuf);
            break;
        case DUMP_PREFIX_OFFSET:
            printf("%s %s %.8x: %s\n", level, prefix_str, i, linebuf);
            break;
        default:
            printf("%s %s %s\n", level, prefix_str, linebuf);
            break;
        }
    }

}

#if 0
int buf[1024*1024];
int main()
{
    memset(buf, 'f', sizeof (buf));
    print_hex_dump("INFO", "test", DUMP_PREFIX_OFFSET,
                   16, 1, buf, sizeof(buf), 1);
    return 0;
}
#endif
